﻿using System;
using System.Collections.Generic;

namespace Com.PhilChuang.Utils
{
	public class ServiceLocatorBase : IServiceLocator
	{
		/// <summary>
		/// Maps interface type to ServiceLocatorHelper
		/// </summary>
		private Dictionary<Type, ServiceLocatorHelper> m_Helpers;

		/// <summary>
		/// Maps type interface to instantiated instance
		/// </summary>
		private Dictionary<Type, Object> m_ImplCache;

		protected virtual Func<Type, Object> DefaultInitializer
		{ get { return (t) => Activator.CreateInstance (t); } }

		public ServiceLocatorBase ()
		{
			m_Helpers = new Dictionary<Type, ServiceLocatorHelper> ();
			m_ImplCache = new Dictionary<Type, object> ();
		}

		public void AddMapping (Type implType)
		{
			AddMapping (implType, implType, true);
		}

		public void AddMapping (Type implType, bool cacheImpl)
		{
			AddMapping (implType, implType, cacheImpl);
		}

		public void AddMapping (Type intfcType, Type implType)
		{
			AddMapping (intfcType, implType, true);
		}

		public void AddMapping (Type intfcType, Type implType, bool cacheImpl)
		{
			AddMapping (intfcType, implType, cacheImpl, DefaultInitializer);
		}

		public void AddMapping (Type intfcType, Type implType, bool cacheImpl, Func<Type, Object> init)
		{
			m_Helpers[intfcType] = new ServiceLocatorHelper () {
			                                        	InterfaceType = intfcType,
			                                        	ImplementationType = implType,
			                                        	Cache = cacheImpl,
			                                        	Initializer = init
			                                        };
		}

		public T GetImpl<T> ()
		{
			var intfcType = typeof (T);
			if (m_ImplCache.ContainsKey (intfcType))
				return (T) m_ImplCache[intfcType];

			if (!m_Helpers.ContainsKey (intfcType))
				throw new ArgumentException ("Can't get impl for {0}".FormatWith (intfcType.Name));

			var helper = m_Helpers[intfcType];

			var impl = (T) helper.Initializer.Invoke (helper.ImplementationType);
			// TODO check to see if impl has Disposed event handler, tie into it to remove from cache

			if (helper.Cache)
				m_ImplCache[intfcType] = impl;

			return impl;
		}

		public void CacheImpl<T> (T impl)
		{
			m_ImplCache[typeof (T)] = impl;
		}

		private struct ServiceLocatorHelper
		{
			public Type InterfaceType { get; set; }
			public Type ImplementationType { get; set; }
			public bool Cache { get; set; }
			public Func<Type, Object> Initializer { get; set; }
		}
	}
}
